/*******************************************************************************
 * Copyright (c) 2001, 2002 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sourceforge.perforce.core.IResourceStateChangeListener;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.syncinfo.IResourceStateDelta;
import net.sourceforge.perforce.ui.internal.Policy;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IDecoratorManager;

/**
  * XXX TODO: Optimizations ...
  * 
  * @version $Revision: 74 $
  * @author  <a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class PerforceDecorator extends LabelProvider 
	implements ILabelDecorator, IResourceStateChangeListener, IDecorationNotifier		
{
	// -------------------------------------------------------------------- Private Static Variables
	
	/** Stores the singleton instance of the decorator. */
	private static PerforceDecorator instance;

	// --------------------------------------------------------------------------- Private Variables

	/** Resource that need to be decorated. */
	private Set decoratorNeedsUpdating = Collections.synchronizedSet(new HashSet());
	/** The decorator cache. */
	private Map cache = Collections.synchronizedMap(new HashMap());
	/** Stores a reference to the decorator thread. */
	private Thread decoratorUpdateThread;	
	/** The OverlayIconCache. */
	private OverlayIconCache iconCache = new OverlayIconCache();
	/** Indicates decorator shutdown. */
	private boolean shutdown = false;
	
	// ------------------------------------------------------------------------- Public Constructors

	/** Constructor for PerforceDecorator. */
	public PerforceDecorator()
	{
		Policy.assertNull(instance);
		instance = this;

		decoratorUpdateThread = 
			new Thread(new PerforceDecorationRunnable(this), "Perforce Decorator");    //$NON-NLS-1$
		decoratorUpdateThread.start();
		
		PerforceCorePlugin.getPlugin().addResourceStateChangeListener(this);
	}

	// ----------------------------------------------------------------------- Public Static Methods

	/** Call this method to shutdown the decorator. */
	public static void shutdown() 
	{
		if (instance != null)
		{
			instance.dispose();
		}
	}

	/** Call this method to refresh the decorator (drop cache, redraw). */
	public static void refresh() 
	{
		PerforceDecorator activeDecorator = getActiveP4Decorator();
		
		if (activeDecorator == null)
		{
			return;
		}
		
		activeDecorator.clearCache();
		activeDecorator.fireLabelProviderChanged(new LabelProviderChangedEvent(activeDecorator));
	}


	// ------------------------------------------------------------------------------ Public Methods

	/** @see IResourceStateChangeListener#resourceStateChanged(IResource[]) */
	public void resourceStateChanged(IResourceStateDelta[] resourceStateDeltas)
	{
		List resourcesToDecorate = new ArrayList();
		for (int i = 0, length = resourceStateDeltas.length; i < length; i++)
		{
			if (cache.containsKey(resourceStateDeltas[i].getResource()))
			{
				if (resourceStateDeltas[i].getKind() != IResourceStateDelta.REMOVED)
				{
					resourcesToDecorate.add(resourceStateDeltas[i].getResource());
				}
				else
				{
					cache.remove(resourceStateDeltas[i].getResource());
				}
			}
		}
		
		addResourcesToBeDecorated((IResource[]) resourcesToDecorate.toArray(new IResource[0]));		
	}

	/** @see IResourceStateChangeListener#changelistStateChanged(IChangelist[]) */
	public void changelistStateChanged(IChangelist[] changelists) 
	{
	}
	
	/** @see IResourceStateChangeListener#changelistStateChanged(IPerforceServer) */
	public void changelistStateChanged(IPerforceServer[] servers) 
	{
	}

	/** @see ILabelDecorator#decorateImage(Image, Object) */
	public Image decorateImage(Image image, Object element)
	{
		IResource resource = getResource(element);
		if (resource == null || image == null || resource.getType() == IResource.ROOT)
		{
			return image;
		}
		
		PerforceDecoration decoration = (PerforceDecoration) cache.get(resource);
		if (decoration == null)
		{
			addResourcesToBeDecorated(new IResource[] { resource });
			return image;
		}
		else
		{
			List overlays = decoration.getOverlays();
			if (overlays != null)
			{
				return iconCache.getImageFor(
					new DecoratorOverlayIcon(
						image, 
						(ImageDescriptor[]) overlays.toArray(
							new ImageDescriptor[overlays.size()])));
			}
		}
		return image;
	}

	/** @see ILabelDecorator#decorateText(String, Object) */
	public String decorateText(String text, Object element)
	{
		IResource resource = getResource(element);
		if (resource == null || text == null || resource.getType() == IResource.ROOT)
		{
			return text;
		}
		
		PerforceDecoration decoration = (PerforceDecoration) cache.get(resource);
		if (decoration == null)
		{
			addResourcesToBeDecorated(new IResource[] { resource });
			return text;
		}
		else
		{
			String format = P4DecoratorConfiguration.getFormat(resource);
			if (format == null)
			{
				return text;
			}
			
			Map bindings = decoration.getBindings();
			if (bindings.isEmpty())
			{
				return text;
			}
			
			bindings.put(P4DecoratorConfiguration.RESOURCE_NAME, text);
			
			return P4DecoratorConfiguration.bind(format, bindings);
		}
	}

	/** Call this method to mark the given resources as "to be decorated".
	  *
	  * @param resources the resources. 
	  */
	public synchronized void addResourcesToBeDecorated(IResource[] resources)
	{
		if (resources.length > 0)
		{
			for (int i = 0, length = resources.length; i < length; i++)
			{
				decoratorNeedsUpdating.add(resources[i]);
			}
				
			notify();
		}
	}

	/** @see IDecorationNotifier#decorated(IResource, PerforceDecoration) */
	public void decorated(IResource[] resources, PerforceDecoration[] decorations)
	{
		List events = new ArrayList();
		if (!shutdown)
		{
			for (int i = 0, length = resources.length; i < length; i++)
			{
				IResource resource = resources[i];
				if (resource.exists())
				{
					cache.put(resource, decorations[i]);
					events.add(new LabelProviderChangedEvent(this, resource));
				}
			}
			
			postLabelEvents(
				(LabelProviderChangedEvent[]) events.toArray(new LabelProviderChangedEvent[0]));
		}
	}

	/** @see IDecorationNotifier#next() */
	public synchronized IResource next()
	{
		try 
		{
			if (shutdown) 
			{
				return null;
			}
			
			if (decoratorNeedsUpdating.isEmpty())
			{ 
				wait();
			}
	
			if (shutdown)
			{ 
				return null;
			}

			Iterator it = decoratorNeedsUpdating.iterator();
	
			IResource result = (IResource) it.next();
			it.remove();
	
			return result;
		} 
		catch (InterruptedException e) 
		{
			// Ignored!
		}		
		
		return null;
	}

	/** @see IDecorationNotifier#remaining() */
	public int remaining()
	{
		return decoratorNeedsUpdating.size();
	}

	/** Called this method to dispose the decorator. */
	public void dispose() 
	{
		super.dispose();

		PerforceCorePlugin.getPlugin().removeResourceStateChangeListener(this);
		
		decoratorNeedsUpdating.clear();
		cache.clear();

		shutdownAll();

		iconCache.disposeAll();		
		instance = null;
	}

	// ---------------------------------------------------------------------- Private Static Methods

	/** Call this method to retrieve the currently active PerforceDecorator.
	  *
	  * @return the active perforce decorator. 
	  */
	private static PerforceDecorator getActiveP4Decorator() 
	{
		IDecoratorManager manager = 
			PerforceUIPlugin.getPlugin().getWorkbench().getDecoratorManager();
		
		if (manager.getEnabled("net.sourceforge.perforce.ui.decorator"))
		{
			return (PerforceDecorator) 
				manager.getLabelDecorator("net.sourceforge.perforce.ui.decorator");
		}
		
		return null;
	}	
	
	// ----------------------------------------------------------------------------- Private Methods
	
	/** Called to clear the cache. */
	private void clearCache() 
	{
		cache.clear();
	}

	/** @see IResourceStateChangeListener#resourceStateChanged(IResource[]) */	
	private IResource getResource(Object object)
	{
		if (object instanceof IResource)
		{
			return (IResource) object;
		}
		else
		if (object instanceof IAdaptable)
		{
			return (IResource) ((IAdaptable) object).getAdapter(IResource.class);
		}
		else
		{
			return null;
		}
	}

	/** Called to fire label events to notify the views about changed decorations.
	  *
	  * @param events the events to fire. 
	  */
	private void postLabelEvents(final LabelProviderChangedEvent[] events) 
	{
		// now post the change events to the UI thread
		if (events.length > 0) 
		{
			Display.getDefault().asyncExec(new Runnable() 
			{
				public void run() 
				{
					for (int i = 0; i < events.length; i++)
					{
						fireLabelProviderChanged(events[i]);
					}
				}
			});
		}
	}
	 
	/** Stops the decorator thread. */
	private void shutdownAll() 
	{
		shutdown = true;
		
		synchronized (this)
		{
			notifyAll();
		}
		
		try 
		{
			decoratorUpdateThread.join();
		}
		catch (InterruptedException e) 
		{
			// Ignored!
		}
	}

	// ---------------------------------------------------------------------- Private Nested Classes
	
	/** The decorator's OverlayIcon. */
	private static class DecoratorOverlayIcon extends OverlayIcon 
	{
		// --------------------------------------------------------------------- Public Constructors
		
		/** Constructor for DecoratorOverlayIcon.
		  *
		  * @param base the base iamge.
		  * @param overlays the overlay to apply. 
		  */
		public DecoratorOverlayIcon(Image base, ImageDescriptor[] overlays) 
		{
			super(base, overlays, new Point(base.getBounds().width, base.getBounds().height));
		}
		
		// ----------------------------------------------------------------------- Protected Methods
		
		/** @see OverlayIcon#drawOverlays(ImageDescriptor[]) */
		protected void drawOverlays(ImageDescriptor[] overlays) 
		{
			for (int i = overlays.length - 1; i >= 0; --i) 
			{
				ImageData imageData = overlays[i].getImageData();
				drawImage(imageData, 0, 0);
			}
		}
	}


}
